﻿// <copyright file="IEcoScriptLanguageService.cs" company="Codeplex project Eco.Contrib"> 
// The MIT License (MIT) 
// </copyright> 
// <author>Peter Buchmann</author> 

namespace Eco.Contrib
{
    using System;
    using Eco.Handles;
    using Eco.ObjectRepresentation;

    /// <summary>
    /// IEcoScriptLanguageService: EcoScriptLanguage EcoService 
    /// </summary>
    public interface IEcoScriptLanguageService
    {
        /// <summary>
        /// Occurs when [is active event].
        /// </summary>
        event EventHandler<EslEventArgs> IsActiveEvent;

        /// <summary>
        /// Occurs when [write event].
        /// </summary>
        event EventHandler<EslEventArgs> WriteEvent;

        /// <summary>
        /// Writes the line.
        /// </summary>
        /// <param name="value">The string line.</param>
        void WriteLine(string value);

        /// <summary>
        /// Writes the specified string line.
        /// </summary>
        /// <param name="value">The string line.</param>
        void Write(string value);

        /// <summary>
        /// Determines whether this instance is active.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this instance is active; otherwise, <c>false</c>.
        /// </returns>
        bool IsActive();

        /// <summary>
        /// Registers the script.
        /// </summary>
        /// <param name="scriptName">Name of the script.</param>
        /// <param name="script">The script.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>Registered EcoScript</returns>
        EcoScript RegisterScript(string scriptName, string script, string parameters);

        /// <summary>
        /// Executes the specified script.
        /// </summary>
        /// <param name="script">The script.</param>
        /// <returns>Return value of executed function</returns>
        IElement Execute(EcoScript script);

        /// <summary>
        /// Executes the specified script name.
        /// </summary>
        /// <param name="scriptName">Name of the script.</param>
        /// <returns>Return value of executed function</returns>
        IElement Execute(string scriptName);

        /// <summary>
        /// Executes the specified script.
        /// </summary>
        /// <param name="script">The script.</param>
        /// <param name="variables">The variables.</param>
        /// <returns>Return value of executed function</returns>
        IElement Execute(EcoScript script, IModifiableVariableList variables);

        /// <summary>
        /// Executes the specified script name.
        /// </summary>
        /// <param name="scriptName">Name of the script.</param>
        /// <param name="variables">The variables.</param>
        /// <returns>Return value of executed function</returns>
        IElement Execute(string scriptName, IModifiableVariableList variables);
    }

    /// <summary>
    /// EcoScriptLanguageService, EcoSpace-Service
    /// </summary>
    public class EcoScriptLanguageService : IEcoScriptLanguageService, IDisposable
    {
        /// <summary>
        /// eslEcoSpace: internal EcoSpace, contains the ESL infrastructure
        /// </summary>
        private EslEcoSpace eslEcoSpace = null;

        /// <summary>
        /// ecoSpace: external EcoSpace that contains the objects on which the ESL operates
        /// </summary>
        private EcoSpace ecoSpace = null;

        /// <summary>
        /// onIsActive: Delegate that returns true when script ist still active
        /// </summary>
        private EventHandler<EslEventArgs> onIsActive;

        /// <summary>
        /// onWrite: Delegate used to redirect line output
        /// </summary>
        private EventHandler<EslEventArgs> onWrite;

        /// <summary>
        /// Initializes a new instance of the <see cref="EcoScriptLanguageService"/> class.
        /// </summary>
        /// <param name="ecoSpace">The ecospace.</param>
        public EcoScriptLanguageService(EcoSpace ecoSpace)
        {
            this.eslEcoSpace = new EslEcoSpace();
            this.eslEcoSpace.Active = true;

            this.ecoSpace = ecoSpace;
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="EcoScriptLanguageService"/> class.
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="EcoScriptLanguageService"/> is reclaimed by garbage collection.
        /// </summary>
        ~EcoScriptLanguageService()
        {
            this.Dispose(false);
        }

        /// <summary>
        /// Occurs when [is active event].
        /// </summary>
        public event EventHandler<EslEventArgs> IsActiveEvent
        {
            add { this.onIsActive += value; }
            remove { this.onIsActive -= value; }
        }

        /// <summary>
        /// Occurs when [write event].
        /// </summary>
        public event EventHandler<EslEventArgs> WriteEvent
        {
            add { this.onWrite += value; }
            remove { this.onWrite -= value; }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Determines whether this instance is active.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this instance is active; otherwise, <c>false</c>.
        /// </returns>
        public bool IsActive()
        {
            if (this.onIsActive != null)
            {
                EslEventArgs e = new EslEventArgs();
                this.onIsActive(this, e);
                return e.IsActive;
            }

            return true;
        }

        /// <summary>
        /// Writes the line.
        /// </summary>
        /// <param name="value">The string line.</param>
        public void WriteLine(string value)
        {
            if (this.onWrite != null)
            {
                this.onWrite(this, new EslEventArgs(value + Environment.NewLine));
            }
        }

        /// <summary>
        /// Writes the specified string line.
        /// </summary>
        /// <param name="value">The string line.</param>
        public void Write(string value)
        {
            if (this.onWrite != null)
            {
                this.onWrite(this, new EslEventArgs(value));
            }
        }

        /// <summary>
        /// Registers the script.
        /// </summary>
        /// <param name="scriptName">Name of the script.</param>
        /// <param name="script">The script.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>Registered EcoScript</returns>
        public EcoScript RegisterScript(string scriptName, string script, string parameters)
        {
            try
            {
                // delete old script is exists
                EcoScript ecoScript = this.eslEcoSpace.Ocl.Evaluate("EcoScript.AllInstances->select(Name='" + scriptName + "')->first").GetValue<EcoScript>();
                if (ecoScript != null)
                {
                    ecoScript.AsIObject().Delete();
                }

                ecoScript = new EcoScript(this.eslEcoSpace, scriptName, script, parameters);
                ecoScript.Parse();
                return ecoScript;
            }
            catch (Exception ex)
            {
                throw new EcoContribException("Error registering script " + scriptName + ": " + ex.Message, ex);
            }
        }

        /// <summary>
        /// Executes the specified script.
        /// </summary>
        /// <param name="script">The script.</param>
        /// <returns>
        /// Return value of executed function
        /// </returns>
        public IElement Execute(EcoScript script)
        {
            if (script == null)
            {
                throw new EcoContribException("Parameter script is null");
            }

            return script.Execute(this.ecoSpace);
        }

        /// <summary>
        /// Executes the specified script.
        /// </summary>
        /// <param name="script">The script.</param>
        /// <param name="variables">The imv variables.</param>
        /// <returns>
        /// Return value of executed function
        /// </returns>
        public IElement Execute(EcoScript script, IModifiableVariableList variables)
        {
            if (script == null)
            {
                throw new EcoContribException("Parameter script is null");
            }

            if (variables == null)
            {
                return script.Execute(this.ecoSpace);
            }
            else
            {
                return script.Execute(this.ecoSpace, variables);
            }
        }

        /// <summary>
        /// Executes the specified script name.
        /// </summary>
        /// <param name="scriptName">Name of the script.</param>
        /// <param name="variables">The imv variables.</param>
        /// <returns>Return value of executed function</returns>
        public IElement Execute(string scriptName, IModifiableVariableList variables)
        {
            try
            {
                EcoScript script = this.eslEcoSpace.Ocl.Evaluate("EcoScript.AllInstances->select(Name='" + scriptName + "')->first").GetValue<EcoScript>();
                if (script == null)
                {
                    throw new EcoContribException("Was not able to find registered script.");
                }

                return Execute(script, variables);
            }
            catch (Exception ex)
            {
                throw new EcoContribException("Error in execution of script " + scriptName + ": " + ex.Message, ex);
            }
        }

        /// <summary>
        /// Executes the specified script name.
        /// </summary>
        /// <param name="scriptName">Name of the script.</param>
        /// <returns>Return value of executed function</returns>
        public IElement Execute(string scriptName)
        {
            return Execute(scriptName, null);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.eslEcoSpace != null)
                {
                    this.eslEcoSpace.Dispose();
                }
            }
        }
    }
}
